package com.novoda.runbuddy.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.Map;

import android.content.Context;
import android.location.Location;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.database.sqlite.SQLiteDatabase;

import com.novoda.runbuddy.model.Run;
import com.novoda.runbuddy.model.ListOfRuns;
import com.novoda.runbuddy.provider.ActivityID;
import com.novoda.runbuddy.util.LocationUtils;
import com.novoda.runbuddy.model.Route;
import com.novoda.runbuddy.model.CheckPoint;
import com.novoda.runbuddy.model.CommonGeoLocation;
import com.novoda.runbuddy.model.RouteSegment;

/*******************************************************************************
 * This class contains methods related to the canned jogs functionality, used
 * for testing.
 */
public class CannedRunManagerImpl implements ICannedRunManager {

	private double				runSpeed;
	private int					runId;
	private ListOfRuns			cannedJogs;
	private double				distance;																	// distance
	private final int			simTickTime			= 4000;												// update
	private static final String	TAG					= "[RBuddy]: " + CannedRunManagerImpl.class.getName();
	private boolean				runSimLocationThread;
	private Thread				locationSimThread	= null;

	public CannedRunManagerImpl(Context context) {
		cannedJogs = readCannedRuns(context);

		distance = 0.0;
		runId = 0;
		runSpeed = 6.0;
	}

	public int getRunId() {
		return runId;
	}

	public double getSpeed() {
		return runSpeed;
	}

	public void setRunId(int id) {
		runId = id;
	}

	public void setSpeed(double speed) {
		runSpeed = speed;
	}

	private Handler	messageHandler;

	public void startRun(Handler msgHandler) {
		messageHandler = msgHandler;
		distance = 0.0;
		runSimLocationThread = true;
		locationSimThread = new Thread()
		{
			public void run() {
				while (runSimLocationThread) {
					try {
						updateSimLocation();
						sleep(simTickTime);
					} catch (InterruptedException e) {
						// don't care
					}
				}
			}
		};

		locationSimThread.start();

	}

	public void stopRun() {
		runSimLocationThread = false;
		locationSimThread.interrupt();
		locationSimThread = null;
	}

	private int	cannedPointId	= 0;

	private void updateSimLocation() {
		Run currentJog = cannedJogs.get(runId);

		Location newLoc = LocationUtils.getJogLocationFromDistance(currentJog, distance);
		newLoc.setTime(System.currentTimeMillis());

		distance = distance + runSpeed * simTickTime / 1000;

		if (newLoc != null) {
			Message msg = Message.obtain(messageHandler, ActivityID.LOCATION_UPDATE, newLoc);
			messageHandler.sendMessageDelayed(msg, 50);
		} else {
			Log.w(TAG, "Null location for canned pid " + cannedPointId);
		}

		cannedPointId++;
	}

	public static ListOfRuns readCannedRuns(Context ctxt) {
		ListOfRuns jogList = new ListOfRuns();

		InputStream inStream = ctxt.getResources().openRawResource(com.novoda.runbuddy.R.raw.canned_routes);
		BufferedReader in = new BufferedReader(new InputStreamReader(inStream));

		try {
			String line = in.readLine();

			Run newJog = null;

			while (line != null) {
				int bPos = line.indexOf('[');
				if (bPos > 0) {
					String routeName = line.substring(0, bPos);
					int ptId = Integer.parseInt(line.substring(bPos + 1, line.indexOf(']')));
					line = line.substring(line.indexOf(']') + 2);
					double longitude = Double.parseDouble(line.substring(0, line.indexOf(' ')));
					line = line.substring(line.indexOf(' ') + 1);
					double latitude = Double.parseDouble(line);

					if ((newJog == null) || (newJog.getName().compareTo(routeName) != 0)) {
						if (newJog != null) {
							jogList.add(newJog);
						}
						newJog = new Run(routeName, 0, 0);
					}
					Location newLoc = new Location("location");
					newLoc.setLatitude(latitude);
					newLoc.setLongitude(longitude);
					newLoc.setTime(ptId); // This just needs to be unique,
					// time is not used for canned
					// routes
					newJog.addRoutePoint(newLoc);

				}
				line = in.readLine();

			}
			if (newJog != null) {
				jogList.add(newJog);
			}
		} catch (IOException e) {
		}

		return jogList;
	}

	
	/**************************************************
	 * loadCannedRoute is used to create a Route from
	 * canned data.  The set of routePoints is loaded 
	 * from one canned route, and the set of checkpoints
	 * is loaded form another.
	 * @param ctxt - Android context
	 * @param routeName - name of canned route to use for routepoints
	 * @param checkPointName - name of canned route to use for checkPoints
	 * @return
	 */
	public static Route loadCannedRoute(Context ctxt, String routeName, String checkPointName)
	{
		Route cannedRoute = null;
		
		ListOfRuns jogList = readCannedRuns(ctxt);
		Run routeJog = jogList.getRunByName(routeName);
		Run checkpointJog = jogList.getRunByName(checkPointName);
		
		if (routeJog!=null && checkpointJog !=null)
		{
			cannedRoute = new Route(0, 0, routeName, null, System.currentTimeMillis());
			
			//Add Routepoints
			Iterator<Map.Entry<Long, Location>> it = routeJog.getlocationsAndTimes().entrySet().iterator();
			while (it.hasNext()) {
				Location nextLoc = it.next().getValue();
				cannedRoute.addPoint(nextLoc);
			}
				
			//Add Route Segments (checkpoints)
			it = checkpointJog.getlocationsAndTimes().entrySet().iterator();
			int id=0;
			while (it.hasNext()) {
				Location nextLoc = it.next().getValue();
				CommonGeoLocation geo = new CommonGeoLocation(nextLoc);
				CheckPoint checkpt = new CheckPoint(0, geo, 0, "", 0, "");
				RouteSegment segment = new RouteSegment(id++, checkpt, System.currentTimeMillis());
				cannedRoute.addSegment(segment);
			}
			
		}
		
		return cannedRoute;
	}
	
	public static void addRouteToCPDB(SQLiteDatabase db, Route route)
	{
		// Create route
		String query = "INSERT INTO route(_idGeoLocation, modifiedAt, createdAt, name)" + 
							"VALUES(0,'2008-08-26T10:40:00.000','2008-08-26T10:10:00.000', '" + route.name + "');";
		db.execSQL(query);
		long routeId = db.compileStatement("SELECT last_insert_rowid();").simpleQueryForLong();
		
		
		
		//Create routepoints
		
		Iterator<Map.Entry<Long, Location>>  it = route.getlocationsAndTimes().entrySet().iterator();
		int pointNo = 0;
		while (it.hasNext()) {
			Location loc = it.next().getValue();
			query = "INSERT INTO geoLocation(latitude, longitude) VALUES(" + (long)loc.getLatitude() + ", " + (long)loc.getLongitude() + ");";
			db.execSQL(query);
			long geoId = db.compileStatement("SELECT last_insert_rowid();").simpleQueryForLong();

			query = "INSERT INTO routePoint(_idGeoLocation, _idRoute, pointNo) " + 
			            "VALUES(" + geoId + ", " + routeId + ", " + pointNo + ");";
			db.execSQL(query);
			pointNo++;
			
		}
		
		//Create Segments
		int chkPtNo = 0;
		Iterator<RouteSegment> segIt = route.getRouteSegments().iterator();
		while (segIt.hasNext())
		{
			RouteSegment segment = segIt.next();
			query = "INSERT INTO geoLocation(latitude, longitude) VALUES(" + 
			           segment.checkPoint.geo.latitude + ", " + segment.checkPoint.geo.longtiude + ");";
			db.execSQL(query);
			long geoId = db.compileStatement("SELECT last_insert_rowid();").simpleQueryForLong();
			
			query = "INSERT INTO checkPoint(_idGeoLocation) VALUES(" + geoId + ");";
			db.execSQL(query);
			long chkPtId = db.compileStatement("SELECT last_insert_rowid();").simpleQueryForLong();
			
			query = "INSERT INTO routeSegment(_idCheckPoint, _idRoute, checkPointNo) " +
			           "VALUES(" + chkPtId + ", " + routeId + ", " + chkPtNo + ");";
			db.execSQL(query);
			chkPtNo++;
		}
		
		
	}
	
}
